package cn.xueden.system.service.impl;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.xueden.common.result.Result;
import cn.xueden.security.SysUserDetails;
import cn.xueden.system.mapper.RoleMapper;
import cn.xueden.system.mapper.UserDailyLoginDurationMapper;
import cn.xueden.system.mapper.UserMapper;
import cn.xueden.system.model.entity.User;
import cn.xueden.system.model.entity.UserDailyLoginDuration;
import cn.xueden.system.model.form.LoginForm;
import cn.xueden.system.service.IAuthService;
import cn.xueden.system.service.IUserService;
import cn.xueden.util.DateTimeUtil;
import cn.xueden.util.JwtUtil;
import cn.xueden.util.SecretUtils;
import cn.xueden.util.SecurityUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author:梁志杰
 * @date:2025/6/8
 * @description:cn.xueden.system.service.impl
 * @version:1.0
 */
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements IAuthService {

    private static final String HEARTBEAT_KEY_PREFIX = "user:heartbeat:";
    private static final long HEARTBEAT_INTERVAL_MILLIS = 10 * 60 * 1000; // 10分钟


    private final UserMapper userMapper;

  private final  StringRedisTemplate stringRedisTemplate;

    @Value("${online-edu.login.captcha.enabled}")
    private boolean captchaEnabled;

    private final RoleMapper roleMapper;

    private final ObjectMapper objectMapper;

    private final JwtUtil jwtUtil;

    private final UserDailyLoginDurationMapper userDailyLoginDurationMapper;

    /**
     * 登录
     * @param request
     * @param loginForm
     * @return
     */
    @Override
    @SneakyThrows(JsonProcessingException.class)
    public Result<String> login(HttpServletRequest request, LoginForm loginForm) {
        // 先判用户是否通过校验
        String s = stringRedisTemplate.opsForValue().get("isVerifyCode" + request.getSession().getId());
        if (StringUtils.isBlank(s)&&captchaEnabled) {
            return Result.failed("请先验证验证码");
        }
        // 根据用户名获取用户信息
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUserName, loginForm.getUsername());
        User user = userMapper.selectOne(wrapper);
        // 判读用户名是否存在
        if (Objects.isNull(user)) {
            return Result.failed("该用户不存在");
        }
        if(user.getIsDeleted() == 1){
            return Result.failed("该用户已注销");
        }

        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        if (!encoder.matches(SecretUtils.desEncrypt(loginForm.getPassword()), user.getPassword())) {
            return Result.failed("密码错误");
        }
        user.setPassword(null);
       // 根据用户Id获取权限
        List<String> permissions = roleMapper.selectCodeById(user.getRoleId());

        // 数据库获取的权限是字符串springSecurity需要实现GrantedAuthority接口类型，所有这里做一个类型转换
        List<SimpleGrantedAuthority> userPermissions = permissions.stream()
                .map(permission -> new SimpleGrantedAuthority("role_" + permission)).toList();
        // 创建一个sysUserDetails对象，该类实现了UserDetails接口
        SysUserDetails sysUserDetails = new SysUserDetails(user);
        // 把转型后的权限放进sysUserDetails对象
        sysUserDetails.setPermissions(userPermissions);

        // 将用户信息转为字符串
        String userInfo = objectMapper.writeValueAsString(user);

        String token = jwtUtil.createJwt(userInfo, userPermissions.stream().map(String::valueOf).toList());
        // 把token放到redis中
        stringRedisTemplate.opsForValue().set("token" + request.getSession().getId(), token, 30, TimeUnit.MINUTES);
        // 封装用户的身份信息，为后续的身份验证和授权操作提供必要的输入
        // 创建UsernamePasswordAuthenticationToken  参数：用户信息，密码，权限列表
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(sysUserDetails, user.getPassword(), userPermissions);

        // 可选，添加Web认证细节
        usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

        // 用户信息存放进上下文
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        // 清除redis通过校验表示
        stringRedisTemplate.delete("isVerifyCode" + request.getSession().getId());
        return Result.success("登录成功", token);
    }

    /**
     * 获取图片验证码
     * @param request  request对象，获取sessionId
     * @param response response对象，响应图片
     */
    @SneakyThrows(IOException.class)
    @Override
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response) {
        // 生成线性图形验证码的静态方法，参数：图片宽，图片高，验证码字符个数 干扰个数
        LineCaptcha captcha = CaptchaUtil
                .createLineCaptcha(200, 100, 4, 300);

        // 把验证码存放进redis
        // 获取验证码
        String code = captcha.getCode();
        String key = "code" + request.getSession().getId();
        stringRedisTemplate.opsForValue().set(key, code, 5, TimeUnit.MINUTES);
        // 把图片响应到输出流
        response.setContentType("image/jpeg");
        ServletOutputStream os = response.getOutputStream();
        captcha.write(os);
        os.close();
    }

    /**
     * 验证码验证
     * @param request request对象获取sessionId
     * @param code    用户输入的验证码
     * @return
     */
    @Override
    public Result<String> verifyCode(HttpServletRequest request, String code) {
        String key = "code" + request.getSession().getId();
        String rightCode = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isBlank(rightCode)) {
            return Result.failed("验证码已过期");
        }
        if (!rightCode.equalsIgnoreCase(code)) {
            return Result.failed("验证码错误");
        }
        // 验证码校验后redis清除验证码，避免重复使用
        stringRedisTemplate.delete(key);
        // 验证码校验后redis存入校验成功，避免用户登录和注册时不验证验证码直接提交
        stringRedisTemplate.opsForValue().set("isVerifyCode" + request.getSession().getId(), "1", 5, TimeUnit.MINUTES);
        return Result.success("验证码校验成功");
    }

    /**
     * 用户发送心跳，更新最后活跃时间。
     *
     * @return
     */
    @Override
    @SneakyThrows(value = JsonProcessingException.class)
    public Result<String> sendHeartbeat(HttpServletRequest request) {

        String key = HEARTBEAT_KEY_PREFIX + SecurityUtil.getUserId();
        String lastHeartbeatStr = stringRedisTemplate.opsForValue().getAndDelete(key);
        LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
        stringRedisTemplate.opsForValue().set(key, now.toString());
        if (lastHeartbeatStr != null) {
            LocalDateTime lastHeartbeat = LocalDateTime.parse(lastHeartbeatStr);
            Duration durationSinceLastHeartbeat = Duration.between(lastHeartbeat, LocalDateTime.now(ZoneOffset.UTC));
            LocalDate date = DateTimeUtil.getDate();
            // 实现累加逻辑，比如更新数据库中的记录
            LambdaQueryWrapper<UserDailyLoginDuration> userDailyLoginDurationLambdaQueryWrapper = new LambdaQueryWrapper<>();
            userDailyLoginDurationLambdaQueryWrapper.eq(UserDailyLoginDuration::getUserId,SecurityUtil.getUserId())
                    .eq(UserDailyLoginDuration::getLoginDate, date);
            List<UserDailyLoginDuration> userDailyLoginDurations =
                    userDailyLoginDurationMapper.selectList(userDailyLoginDurationLambdaQueryWrapper);
            if(userDailyLoginDurations.isEmpty()){
                UserDailyLoginDuration userDailyLoginDuration = new UserDailyLoginDuration();
                userDailyLoginDuration.setUserId(SecurityUtil.getUserId());
                userDailyLoginDuration.setLoginDate(date);
                userDailyLoginDuration.setTotalSeconds(0);
                userDailyLoginDurationMapper.insert(userDailyLoginDuration);
            }else {
                UserDailyLoginDuration userDailyLoginDuration = new UserDailyLoginDuration();
                userDailyLoginDuration.setTotalSeconds(userDailyLoginDurations.get(0)
                        .getTotalSeconds()+(int)durationSinceLastHeartbeat.getSeconds());
                userDailyLoginDuration.setId(userDailyLoginDurations.get(0).getId());
                userDailyLoginDurationMapper.updateById(userDailyLoginDuration);
            }
        }
        ArrayList<String> permissions = new ArrayList<>();
        permissions.add(SecurityUtil.getRole());
        SysUserDetails principal = (SysUserDetails) (SecurityContextHolder.getContext().getAuthentication().getPrincipal());
        User user = principal.getUser();
        String string = objectMapper.writeValueAsString(user);
        String jwt = jwtUtil.createJwt(string, permissions);
        stringRedisTemplate.opsForValue().set("token" + request.getSession().getId(), jwt, 30, TimeUnit.MINUTES);
        return Result.success("请求成功",jwt);
    }
}
